﻿/**
 * Copyright (C) 2012-2013 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Spi;
using System.Reflection;
using CGLib;
using NGuice.Inject.Internal.Util;

namespace NGuice.Inject.Internal
{
    //complete！
    /// <summary>
    /// 调用一个可注入的方法（其中使用.net实现的cglib，可能有问题）
    /// </summary>
    internal sealed class SingleMethodInjector : SingleMemberInjector
    {
        private readonly InjectorImpl.MethodInvoker methodInvoker;
        private readonly SingleParameterInjector[] parameterInjectors;
        private readonly InjectionPoint injectionPoint;

        internal SingleMethodInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors)
        {
            this.injectionPoint = injectionPoint;
            MethodInfo method = (MethodInfo)injectionPoint.Member;
            methodInvoker = CreateMethodInvoker(method);
            parameterInjectors = injector.GetParametersInjectors(injectionPoint.Dependencies, errors);
        }

        private InjectorImpl.MethodInvoker CreateMethodInvoker(MethodInfo method)
        {

            // We can't use FastMethod if the method is private.

            if (!method.IsPrivate && !method.IsFamily)
            {
                /*if[AOP]*/
                //将使用Emit动态生成方法调用
                try
                {
                    FastMethod fastMethod = FastClass.CreateMethod(method);
                    return new FastMethodInvoker(fastMethod);
                    //return new MethodInvoker() {
                    //  public Object invoke(Object target, Object... parameters)
                    //      throws IllegalAccessException, InvocationTargetException {
                    //    return fastMethod.invoke(target, parameters);
                    //  }
                    //};
                }
                catch (Exception e) {/* fall-through */}
                /*end[AOP]*/
            }

            //if (!method.IsPublic || !method.DeclaringType.IsPublic) {
            //  method.setAccessible(true);
            //}

            return new InternalMethodInvoker(method);
        }

        private class FastMethodInvoker : InjectorImpl.MethodInvoker
        {
            private FastMethod fastMethod;

            public FastMethodInvoker(FastMethod fastMethod)
            {
                this.fastMethod = fastMethod;
            }

            public object Invoke(object target, params object[] parameters)
            {
                return fastMethod.Invoke(target, parameters);
            }
        }
        private class InternalMethodInvoker : InjectorImpl.MethodInvoker
        {
            private MethodInfo method;

            public InternalMethodInvoker(MethodInfo method)
            {
                this.method = method;
            }

            public object Invoke(object target, params object[] parameters)
            {
                return method.Invoke(target, BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, parameters, null);
            }
        }

        public void Inject(Errors errors, InternalContext context, object o)
        {
            object[] parameters;
            try
            {
                parameters = SingleParameterInjector<object>.GetAll(errors, context, parameterInjectors);
            }
            catch (ErrorsException e)
            {
                errors.Merge(e.Errors);
                return;
            }

            try
            {
                methodInvoker.Invoke(o, parameters);
            }
            catch (MethodAccessException e)
            {
                throw new AssertionError("a security manager is blocking us", e); // a security manager is blocking us, we're hosed
            }
            catch (TargetInvocationException userException)
            {
                Exception cause = userException.InnerException != null
                    ? userException.InnerException
                    : userException;
                errors.WithSource(injectionPoint).ErrorInjectingMethod(cause);
            }
        }

        public InjectionPoint GetInjectionPoint()
        {
            return injectionPoint;
        }
    }
}
